#pragma once
#ifndef _KH_ENGINE_NET_H
#define _KH_ENGINE_NET_H
#include <Kharlia/Engine/Common.h>
#include <Kharlia/Engine/Units.h>

namespace kharlia { namespace engine {

enum EPacketID {
    ID_DEBUG_MESSAGE = ID_USER_PACKET_ENUM,
    ID_CHANNEL_OPEN_REQUEST,
    ID_CHANNEL_OPEN_REPLY,
    ID_CHANNEL_CLOSE,
    ID_CHANNEL_MESSAGE,
    ID_ACTOR_RPC,
};

const uint16 DEBUG_MESSAGE_MAX_SIZE = 0x100;

enum ENetRole {
    ROLE_NONE,
    ROLE_SIM_PROXY,
    ROLE_AUTO_PROXY,
    ROLE_AUTHORITY
};

enum EFieldType {
    // Basic number types
    FT_INT = 0,
    FT_UINT,
    FT_FLOAT,
    FT_DOUBLE,
    FT_ENUM,
    FT_BOOL,
    // Boost.Python type rwappers
    FT_PYOBJECT,
    FT_PYSTR,
    FT_PYUNICODE,
    // Field types
    FT_VECTOR3F,
    FT_VECTOR3I,
    FT_VECTOR2F,
    FT_VECTOR2I,
    FT_RECTI,
    FT_RECTF,
    FT_SIZEI,
    FT_SIZEF,
    // Everything else
    FT_SHARED_PTR_ACTOR, // shared_ptr<CActor>
    FT_SHARED_PTR_COMPONENT, // shared_ptr<AComponent>
    // Special
    FT_SENTRY = 250,
};

struct FFieldInfo {
    typedef uint16 IndexType;

    // Macro-specified variables
    EFieldType Type; // Variable type
    const char* Name; // Variable name
    uint16 Offset; // Offset of value in its object
    uint16 Size; // Size in bytes for storage
    uint16 ClassSize; // Stores class size in bytes
    uint16 ArraySize; // > 0 if array

    // Initialized variables
    IndexType Index; // Enumerated index
};

#define KH_DECL_CLASSINFO() \
    static const FFieldInfo* GetClassFieldInfo();\
    virtual void GetFullFieldInfo(std::vector<FFieldInfo>& output);

#define KH_BEGIN_CLASSINFO(CLASS, BASECLASS) \
    void CLASS::GetFullFieldInfo(std::vector<FFieldInfo>& output) {\
        BASECLASS::GetFullFieldInfo(output);\
        const FFieldInfo* FieldInfo = CLASS::GetClassFieldInfo();\
        for (int i = 0; FieldInfo[i].Type != FT_SENTRY; i++) {\
            output.push_back(FieldInfo[i]);\
        }\
    }\
    const FFieldInfo* CLASS::GetClassFieldInfo() {\
        typedef CLASS classtypedef;\
        static FFieldInfo nvi[] = {

#define KH_BEGIN_CLASSINFO_NO_BASE(CLASS) \
    void CLASS::GetFullFieldInfo(std::vector<FFieldInfo>& output) {\
        const FFieldInfo* FieldInfo = CLASS::GetClassFieldInfo();\
        for (int i = 0; FieldInfo[i].Type != FT_SENTRY; i++) {\
            output.push_back(FieldInfo[i]);\
        }\
    }\
    const FFieldInfo* CLASS::GetClassFieldInfo() {\
        typedef CLASS classtypedef;\
        static FFieldInfo nvi[] = {

#define KH_END_CLASSINFO() \
            {FT_SENTRY, NULL, 0, 0, 0, 0, 0xFFFF}\
        };\
        return nvi;\
    }

#define KH_DEF_FIELD(name, type) KH_DEF_FIELD_ARRAY(name, type, 0)

#define KH_DEF_FIELD_ARRAY(name, type, size) \
    {type, #name, offsetof(classtypedef, name), TDatamapFieldSizeDeducer<type>::Size, sizeof(classtypedef), size, 0xFFFF},

// Field Sizes

template<EFieldType FieldType>
struct TDatamapFieldSizeDeducer {
    static const int Size = 0;
    static int FieldSize() { return 0; }
};

#define KH_DEF_FIELD_SIZE(FIELDTYPE, FIELDSIZE) \
    template<> struct TDatamapFieldSizeDeducer<FIELDTYPE> { \
        static const int Size = FIELDSIZE;\
        static int GetFieldSize() { return FIELDSIZE; }};

KH_DEF_FIELD_SIZE(FT_INT, sizeof(int))
KH_DEF_FIELD_SIZE(FT_UINT, sizeof(uint))
KH_DEF_FIELD_SIZE(FT_FLOAT, sizeof(float))
KH_DEF_FIELD_SIZE(FT_DOUBLE, sizeof(double))
KH_DEF_FIELD_SIZE(FT_ENUM, sizeof(int))
KH_DEF_FIELD_SIZE(FT_BOOL, sizeof(bool))
KH_DEF_FIELD_SIZE(FT_PYOBJECT, sizeof(object))
KH_DEF_FIELD_SIZE(FT_PYSTR, sizeof(object))
KH_DEF_FIELD_SIZE(FT_PYUNICODE, sizeof(object))
KH_DEF_FIELD_SIZE(FT_VECTOR3F, sizeof(FVector3F))
KH_DEF_FIELD_SIZE(FT_VECTOR3I, sizeof(FVector3I))
KH_DEF_FIELD_SIZE(FT_VECTOR2F, sizeof(FVector2F))
KH_DEF_FIELD_SIZE(FT_VECTOR2I, sizeof(FVector2I))
KH_DEF_FIELD_SIZE(FT_RECTI, sizeof(FRectI))
KH_DEF_FIELD_SIZE(FT_RECTF, sizeof(FRectF))
KH_DEF_FIELD_SIZE(FT_SIZEI, sizeof(FSizeI))
KH_DEF_FIELD_SIZE(FT_SIZEF, sizeof(FSizeF))
KH_DEF_FIELD_SIZE(FT_SHARED_PTR_ACTOR, sizeof(uint32)) // reference

// Functions

inline rn::MessageID GetPacketIdentifier(rn::Packet *p) {
    if (p == NULL)
        return 0xFF;
    if (p->data[0] == ID_TIMESTAMP) {
        RakAssert(p->length > sizeof(rn::MessageID) + sizeof(rn::Time));
        return p->data[sizeof(rn::MessageID) + sizeof(rn::Time)];
    }
    else
        return p->data[0];
}

inline bool IgnorePacketIdentifier(rn::BitStream* bs) {
    if (!bs)
        return false;
    rn::MessageID id;
    bs->Read(id);
    if (id == ID_TIMESTAMP) {
        bs->IgnoreBytes(sizeof(rn::Time) + sizeof(rn::MessageID));
    }
    return true;
}

template <EFieldType FieldType>
int GetFieldSize() { return TDatamapFieldSizeDeducer<FieldType>::Size; }
int GetFieldSize(EFieldType type);

enum EChannelType {
    CHTYPE_NONE = 0,
    CHTYPE_FILE,
    CHTYPE_ACTOR,
};

//class FNetConnection;
//
//class KH_ENGINE_DECLSPEC FNetChannel: public FRefCounted {
//    FNetConnection* Connection;
//    uint16 Index;
//
//public:
//    FNetChannel(FNetConnection* connection, uint16 index);
//
//    KH_INLINE_GET(uint16, Index)
//
//    static void InitClass();
//};
//
//class KH_ENGINE_DECLSPEC FNetConnection: public FRefCounted {
//    RakNet::RakPeerInterface* Peer;
//    RakNet::RakNetGUID GUID;
//    RakNet::SystemAddress Address;
//    std::vector<intrusive_ptr<FNetChannel>> Channels;
//
//public:
//    FNetConnection(RakNet::RakPeerInterface* peer,
//                   RakNet::RakNetGUID guid,
//                   RakNet::SystemAddress address):
//        Peer(peer),
//        GUID(guid),
//        Address(address) {}
//    virtual ~FNetConnection() {}
//
//    KH_INLINE_GET(RakNet::RakNetGUID, GUID)
//    KH_INLINE_GET(RakNet::SystemAddress, Address)
//
//    static void InitClass();
//};

}}

#endif